﻿//
// Copyright 2024 Exhunter Technology Co.Ltd2.
//

using System;
using System.Collections.Generic;

namespace X18.Framework.Core.MachineKey
{
    /// <summary>
    /// 特征码计算算法基类
    /// </summary>
    internal abstract class HwNoUnit
    {
        private const int sect = 5;
        private const int step = 3;
        private const int piece = 5;

        private static char[] dict = { 'E', 'N', '5', '9', 'C', 'B', '2', 'Q', '7', 'D', 'H', '3', '8', 'A', 'G', '6', 'F', 'J', 'I', 'K', 'L', 'M', 'O', 'P', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y' };
        private static Dictionary<char, int> map;
        private static byte[] checks = { 23, 15, 5, 31, 21, 11, 18, 10, 27, 20, 8, 2, 29, 30, 24, 12, 16, 1, 26, 7, 31, 22, 3, 25, 17, 4, 13, 6, 19, 9, 14, 28 };

        static HwNoUnit()
        {
            map = new Dictionary<char, int>();
            for (int i = 0; i < dict.Length; i++)
            {
                map.Add(dict[i], i);
            }
        }

        public abstract char Prefix { get; }

        public abstract bool TryGet(out string no);

        public virtual bool Validate(string no)
        {
            // 判断效验码是否正确
            byte[] bytes = GetBytes(no);
            byte check = GetCheckCode(bytes);
            bool result = check == bytes[sect * step - 1];
            //
            if (result)
            {
                string no0;
                result = TryGet(out no0);
                if (result)
                {
                    byte[] bytes0 = GetBytes(no0);
                    for (int i = 0; i < 8; i++)
                    {
                        result = bytes[i] == bytes0[i];
                        if (!result) break;
                    }
                }
            }
            return result;
        }

        /// <summary>
        /// 将特征码数据转为字符串输出
        /// </summary>
        protected string GetCode(byte[] bytes)
        {
            string result = "";
            byte check = GetCheckCode(bytes);
            bytes[sect * step - 1] = check;

            for (int i = 0; i < sect; i++)
            {
                var datas = new byte[step];
                datas[0] = bytes[i * step + 0];
                datas[1] = bytes[i * step + 1];
                datas[2] = bytes[i * step + 2];
                var tmp = GetCode0(datas, i);
                //
                if (i != 0)
                {
                    result += MachineKey.CodeSplitKey;
                }
                result += tmp;
            }
            return result;
        }

        private string GetCode0(byte[] bytes, int indx)
        {
            ulong h = 0;
            h |= (ulong)(bytes[2]) << 16;
            h |= (ulong)(bytes[1]) << 8;
            h |= (ulong)(bytes[0]);
            //
            string result = "";
            for (int i = 0; i < piece; i++)
            {
                int v = (int)(h >> (i * piece) & 0x1ful);
                var c = dict[v];
                result += (i == piece - 1 && indx == 2 && Prefix != ' ') ? Prefix : c;
            }
            return result;
        }

        protected byte[] GetHashBytes(ulong hash)
        {
            byte[] result = new byte[sect * step];
            var bytes = System.BitConverter.GetBytes(hash);
            for (int i = 0; i < 8; i++)
            {
                result[i] = bytes[i];
            }
            //
            result[8] = 0;
            //
            byte b1 = 0, b2 = 0;
            var random = new Random();
            for (int i = 9; i < sect * step - 1; i++)
            {
                while (b1 == b2)
                {
                    b1 = (byte)random.Next(0, 254);
                }
                b2 = b1;
                result[i] = b1;
            }
            return result;
        }

        /// <summary>
        /// 将特征码字符串转为原始数据
        /// </summary>
        protected byte[] GetBytes(string no)
        {
            byte[] result = new byte[sect * step];
            var items = no.Split(MachineKey.CodeSplitKey);
            for (int i = 0; i < items.Length; i++)
            {
                var item = items[i];
                var datas = GetBytes0(item, i);
                //
                result[i * step + 0] = datas[0];
                result[i * step + 1] = datas[1];
                result[i * step + 2] = datas[2];
            }
            return result;
        }

        private byte[] GetBytes0(string no, int indx)
        {
            byte[] result = new byte[step];
            int len = no.Length;
            if (len > piece) len = piece;
            int h = 0;
            for (int i = 0; i < len; i++)
            {
                char c = no[i];
                int v = map.ContainsKey(c) ? map[c] : 0;
                h |= (v << (i * piece));
            }
            result[0] = (byte)(h >> 0 * 8 & 0xff);
            result[1] = (byte)(h >> 1 * 8 & 0xff);
            result[2] = (byte)(h >> 2 * 8 & 0xff);
            return result;
        }

        protected byte GetCheckCode(byte[] bytes)
        {
            int v = 0;
            for (int i = 0; i < sect * step - 1; i++)
            {
                v += bytes[i] * checks[i];
            }
            v = v & 0xff;
            return (byte)v;
        }
    }
}
